#ifndef VM_HPP
#define VM_HPP 1

#include <cstdint>
#include <vector>
#include "bitinput.hpp"
#include "exception.hpp"

namespace fur
{
   using std::vector;

   const uint32_t VM_MEMSIZE = 0x40000;
   const uint32_t VM_MEMMASK = VM_MEMSIZE - 1;
   const uint32_t VM_GLOBALMEMADDR = 0x3C000;
   const uint32_t VM_GLOBALMEMSIZE = 0x2000;
   const uint32_t VM_FIXEDGLOBALSIZE = 64;
   
   enum class VMCommand
   {
      MOV = 0, CMP = 1, ADD = 2, SUB = 3, JZ = 4, JNZ = 5, INC = 6, DEC = 7,
      JMP = 8, XOR = 9, AND = 10, OR = 11, TEST = 12, JS = 13, JNS = 14,
      JB = 15, JBE = 16, JA = 17, JAE = 18, PUSH = 19, POP = 20, CALL = 21,
      RET = 22, NOT = 23, SHL = 24, SHR = 25, SAR = 26, NEG = 27, PUSHA = 28,
      POPA = 29, PUSHF = 30, POPF = 31, MOVZX = 32, MOVSX = 33, XCHG = 34,
      MUL = 35, DIV = 36, ADC = 37, SBB = 38, PRINT = 39, MOVB = 40, MOVD = 41,
      CMPB = 42, CMPD = 43, ADDB = 44, ADDD = 45, SUBB = 46, SUBD = 47, INCB = 48,
      INCD = 49, DECB = 50, DECD = 51, NEGB = 52, NEGD = 53, STANDARD = 54
   };

   enum class VMStandardFilter
   {
      NONE = 0,
      E8 = 1,
      E8E9 = 2,
      ITANIUM = 3,
      RGB = 4,
      AUDIO = 5,
      DELTA = 6,
      UPCASE = 7
   };

   typedef uint32_t VMFlag;
   const uint32_t VMFLAG_FC = 1;
   const uint32_t VMFLAG_FZ = 2;
   const uint32_t VMFLAG_FS = 0x80000000;

   enum class VMOpType
   {
      REG = 0,
      INT = 1,
      REGMEM = 2,
      NONE = 3
   };

   class VMPreparedOperand
   {
   public:
      VMOpType type;
      uint32_t data;
      uint32_t base;
      uint32_t *addr;
   };
   
   class VMPreparedCommand
   {
   public:
      VMPreparedOperand op1;
      VMPreparedOperand op2;
      VMCommand opcode;
      bool bytemode;
   };

   class VMPreparedProgram
   {
   public:
      VMPreparedProgram();
      vector<VMPreparedCommand> cmd;
      uint32_t cmdcount;
      VMPreparedCommand *altcmd;
      vector<uint8_t> globaldata;
      vector<uint8_t> staticdata;
      uint initr[7];
      uint8_t *filtereddata;
      uint32_t filtereddatasize;
   };

   class VisualMachine:private BitInputBase
   {
   public:
      VisualMachine();
      ~VisualMachine();
      bool is_vm_mem(uint32_t *addr) const throw();
      uint32_t getvalue(bool bytemode, uint32_t *addr);
      void setvalue(bool bytemode, uint32_t *addr, uint32_t value);
      uint32_t *get_operand(VMPreparedOperand *cmdop);
      void decode_arg(VMPreparedOperand &op, bool bytemode);
      void optimize(VMPreparedProgram *prg);
      bool execute_code(VMPreparedCommand *preparedcode, uint32_t codesize);
      VMStandardFilter is_standard_filter(uint8_t *code, uint32_t codesize);
      void execute_standard_filter(VMStandardFilter filtertype);
      uint32_t filter_itanium_getbits(uint8_t *data, size_t bitposi, int bitcount);
      void filter_itanium_setbits(uint8_t *data, size_t bitfield, size_t bitposi, int bitcount);
      void prepare(uint8_t *code, size_t codesize, VMPreparedProgram *prg);
      void init();
      void execute(VMPreparedProgram *prg);
      void set_low_endian_value(uint32_t *addr, uint32_t value);
      void set_memory(uint32_t posi, uint8_t *data, uint32_t value);
   private:
      uint8_t *mem;
      uint32_t R[8];
      uint32_t flags;
   };

   uint32_t vm_read_data(BitInputBase &bitinput);
   
} // end of namespace fur

#endif /* vm.hpp */
